package it.uniroma3.dia.digraph.algorithm.stackedTri;

import it.uniroma3.dia.digraph.algorithm.utility.Utility;
import it.uniroma3.dia.digraph.algorithm.utility.stackedtri.StackedTriNode;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.graphdrawing.graphml.xmlns.GraphType;
import org.graphdrawing.graphml.xmlns.NodeType;

/**
 * @author Cionzo
 *
 */
public class GenerationBuilder {
	
	
	/**
	 * @param g
	 * @return
	 */
	public static Map<Integer, List<StackedTriNode>> getLayer2Nodes(GraphType g){
		 
		Map<Integer, List<StackedTriNode>> result = new HashMap<Integer, List<StackedTriNode>>();
		Map<String,Set<String>> adjacencyList = Utility.buildAdjacencyMatrix(g);
		Map<String, StackedTriNode> nodeMap = new HashMap<String, StackedTriNode>();
		StackedTriNode current;
		List<StackedTriNode> outNodes = getOuterplanarNodes(g.getNodeCollection().getNode());
		
		//nodes will be compared by the number of their parents
		Comparator<StackedTriNode> comparator = new Comparator<StackedTriNode>() {

			@Override
			public int compare(StackedTriNode o1, StackedTriNode o2) {
				return o2.getParentNumber()-o1.getParentNumber();
			}
		};			
		
		//init nodemap
		buildNodeMap(nodeMap, outNodes);		
		
		//manage nodes on convex hull
		manageNodesOnConvexHull(result, adjacencyList, nodeMap, outNodes);
				
		//set level to remaining nodes		
		while(outNodes.size()!=0){
			
			//retrieve the node to set 
			Collections.sort(outNodes, comparator);
			current = outNodes.get(0);
			
			//check parent number for this node
			checkParentNumber(current);	
			
			//set the depth 
			setDepth(current);
			
			//update current node barycentric coords
			current.calculateLambda();
			
			//add this node as parent to its deeper neighbours
			updateNeighbors(current, adjacencyList, nodeMap);		
			
			//add to result
			addToResult(result, current);		
			
			//remove this node from current list
			outNodes.remove(current);	
		}		
		
		return result;
	}

	/**
	 * @param nodeMap
	 * @param outNodes
	 */
	private static void buildNodeMap(Map<String, StackedTriNode> nodeMap,
			List<StackedTriNode> outNodes) {
		//init nodemap
		for (StackedTriNode n : outNodes){
			nodeMap.put(n.getId(), n);
		}
	}

	/**
	 * @param current
	 * @throws Error
	 */
	private static void checkParentNumber(StackedTriNode current) throws Error {
		//check parent number: must be 3
		if (current.getParentNumber()!=3){
			throw new Error("something wrong in extracting node "+
					current.getNode().getId()+
					" from heap. parentNumber="+current.getParentNumber());
		}
	}

	/**
	 * @param result
	 * @param adjacencyList
	 * @param nodeMap
	 * @param outNodes
	 */
	private static void manageNodesOnConvexHull(
			Map<Integer, List<StackedTriNode>> result,
			Map<String, Set<String>> adjacencyList,
			Map<String, StackedTriNode> nodeMap, List<StackedTriNode> outNodes) {
		
		//nodes on convex hull have depth=0
		result.put(0, getNodesOnConvexHull(outNodes));
		// set level to nodes on convex hull
		for (StackedTriNode n: result.get(0)){
			n.setDepth(0);
			updateNeighbors(n, adjacencyList, nodeMap);
			outNodes.remove(n);
		}
	}

	/**
	 * @param result
	 * @param current
	 */
	private static void addToResult(Map<Integer, List<StackedTriNode>> result,
			StackedTriNode current) {
		
		List<StackedTriNode> nodesAtLevel;
		nodesAtLevel = result.get(current.getDepth()); 
		
		if (nodesAtLevel==null){
			nodesAtLevel = new LinkedList<StackedTriNode>();
			result.put(current.getDepth(), nodesAtLevel);
		}
		nodesAtLevel.add(current);
	}

	/**
	 * @param current
	 */
	private static void setDepth(StackedTriNode current) {
		// set current node depth to
		// 1+ max(parents' depth)
		current.setDepth(1+ Math.max(
				current.getParents()[0].getDepth(), 
				Math.max(current.getParents()[1].getDepth(), 
						current.getParents()[2].getDepth())));
	}

	private static void updateNeighbors(StackedTriNode n,
			Map<String, Set<String>> adjacencyList,
			Map<String, StackedTriNode> nodeMap) {
		
		
//		System.out.print(n.getId()+": "+n.getX()+" "+n.getY()+" "+"updating neighbours ");
		for (String neighborId: adjacencyList.get(n.getId())){
			
			if (nodeMap.get(neighborId).getParentNumber()<3 && nodeMap.get(neighborId).getDepth()>n.getDepth()){
				nodeMap.get(neighborId).addParent(n);
//				System.out.print(neighborId+" ");
			}
		}
		System.out.println();
		
		
	}

	private static List<StackedTriNode> getNodesOnConvexHull(
			List<StackedTriNode> outNodes) {
		
		return Utility.getNodesOnConvexHull(outNodes);
	}

	private static List<StackedTriNode> getOuterplanarNodes(List<NodeType> nodes) {
		List<StackedTriNode> result = new LinkedList<StackedTriNode>();
		
		for (NodeType n : nodes)
			result.add(new StackedTriNode(n));	
		
		return result;
	}

}
